package nl.utwente.fmt.vertalerbouw.tarrh.checker;

import java.util.Stack;

import nl.utwente.fmt.vertalerbouw.tarrh.symboltable.SymbolTable;
import nl.utwente.fmt.vertalerbouw.tarrh.symboltable.SymbolTableException;
import nl.utwente.fmt.vertalerbouw.tarrh.treewalker.TreeWalker;

public class CheckerTreeWalker implements
		TreeWalker<CheckerException, Object, Object, Object, CheckerClassEntry, CheckerFunctionEntry, Object> {
	private final CheckerClassEntry booleanType = new CheckerClassEntry();
	private CheckerClassEntry currentClass = null;
	private CheckerFunctionEntry currentFunction = null;
	private final CheckerClassEntry integerType = new CheckerClassEntry();
	private final Stack<CheckerClassEntry> returnTypeStack = new Stack<CheckerClassEntry>();
	private final CheckerClassEntry stringType = new CheckerClassEntry();
	private SymbolTable<CheckerVariableEntry, CheckerFunctionEntry, CheckerClassEntry> symbolTable = new SymbolTable<CheckerVariableEntry, CheckerFunctionEntry, CheckerClassEntry>();

	private final CheckerClassEntry voidType = new CheckerClassEntry();

	/**
	 * Called after the occurence of a variable.
	 * 
	 * @param identifier
	 *            the identifier of the variable.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry accessVariable(String identifier) throws CheckerException {
		try {
			return symbolTable.retrieveVariable(identifier).getType();
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called after the occurence of the array index operator.
	 * 
	 * @param expr
	 *            the resulting type from the main expression.
	 * @param index
	 *            the resulting type from the index expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry arrayIndexAccess(CheckerClassEntry expr, CheckerClassEntry index) throws CheckerException {
		if (!expr.isArrayType()) {
			throw new CheckerException("Index operation on non-array value");
		}
		if (!index.equals(integerType)) {
			throw new CheckerException("Index operation with non-integer index");
		}
		return expr.getElementType();
	}

	/**
	 * Called after the occurence of an array index assignment.
	 * 
	 * @param identifier
	 *            the identifier of the variable.
	 * @param index
	 *            the resulting type from the index expression.
	 * @param expr
	 *            the resulting type from the expression containing the value to
	 *            be assigned.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry arrayIndexAssign(String identifier, CheckerClassEntry index, CheckerClassEntry expr)
			throws CheckerException {
		CheckerVariableEntry variable;
		try {
			variable = symbolTable.retrieveVariable(identifier);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (variable.isConstant()) {
			throw new CheckerException("Assignment to constant value");
		}
		if (!variable.getType().isArrayType()) {
			throw new CheckerException("Index assignment to non-array variable");
		}
		if (!index.equals(integerType)) {
			throw new CheckerException("Index operation with non-integer index");
		}
		if (!expr.equals(variable.getType().getElementType())) {
			throw new CheckerException("Assignment right-hand-side is not same type");
		}
		return expr;
	}

	/**
	 * Called after the occurence of an assigment.
	 * 
	 * @param identifier
	 *            the identifier of the variable.
	 * @param expr
	 *            the resulting type from the expression containing the value to
	 *            be assigned.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry assign(String identifier, CheckerClassEntry expr) throws CheckerException {
		CheckerVariableEntry variable;
		try {
			variable = symbolTable.retrieveVariable(identifier);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (variable.isConstant()) {
			throw new CheckerException("Assignment to constant value");
		}
		if (!expr.equals(variable.getType())) {
			throw new CheckerException("Assignment right-hand-side is not same type");
		}
		return variable.getType();
	}

	/**
	 * Called at the start of a class declaration.
	 * 
	 * @param className
	 *            the class name of the new class.
	 */
	@Override
	public void classEnd(String className) {
		currentClass = null;
		symbolTable.closeScope();
	}

	/**
	 * Called at the end of a class declaration.
	 * 
	 * @param className
	 *            the class name of the new class.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void classStart(String className) throws CheckerException {
		currentClass = new CheckerClassEntry();
		try {
			symbolTable.enterClassAndOpenScope(className, currentClass);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called after the occurence of a local variable declaration with an array
	 * type.
	 * 
	 * @param type
	 *            the type of the elements in the array.
	 * @param identifier
	 *            the identifier of the variable.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareArray(String type, String identifier) throws CheckerException {
		CheckerClassEntry typeEntry;
		try {
			typeEntry = symbolTable.retrieveClass(type).getArrayTypeOfThisClass();
			symbolTable.enterVariable(identifier, new CheckerVariableEntry(typeEntry, false));
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (typeEntry.getElementType().equals(voidType)) {
			throw new CheckerException("Variable array declaration with Void type");
		}
	}

	/**
	 * Called after the occurence of a local constant declaration.
	 * 
	 * @param type
	 *            the type of the constant value.
	 * @param identifier
	 *            the identifier of the constant.
	 * @param expr
	 *            the resulting type from the expression containing the value to
	 *            be assigned to the constant.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareConstant(String type, String identifier, CheckerClassEntry expr) throws CheckerException {
		CheckerClassEntry typeEntry;
		try {
			typeEntry = symbolTable.retrieveClass(type);
			symbolTable.enterVariable(identifier, new CheckerVariableEntry(typeEntry, true));
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (!typeEntry.equals(expr)) {
			throw new CheckerException("Constant declaration value is not " + type);
		}
		if (typeEntry.equals(voidType)) {
			throw new CheckerException("Constant declaration with Void type");
		}
	}

	/**
	 * Called after the occurence of a member variable declaration with an array
	 * type.
	 * 
	 * @param type
	 *            the type of the elements in the array.
	 * @param identifier
	 *            the identifier of the array.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareMemberArray(String type, String identifier) throws CheckerException {
		declareArray(type, identifier);
	}

	/**
	 * Called after the occurence of a member constant declaration.
	 * 
	 * @param type
	 *            the type of the constant value.
	 * @param identifier
	 *            the identifier of the constant.
	 * @param expr
	 *            the resulting type from the expression containing the value to
	 *            be assigned to the constant.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareMemberConstant(String type, String identifier, CheckerClassEntry expr) throws CheckerException {
		declareConstant(type, identifier, expr);
	}

	/**
	 * Called after the occurence of a member variable declaration.
	 * 
	 * @param type
	 *            the type of the variable value.
	 * @param identifier
	 *            the identifier of the variable.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareMemberVariable(String type, String identifier) throws CheckerException {
		declareVariable(type, identifier);
	}

	/**
	 * Called after the occurrence of a local variable declaration.
	 * 
	 * @param type
	 *            the type of the variable value.
	 * @param identifier
	 *            the identifier of the variable.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void declareVariable(String type, String identifier) throws CheckerException {
		CheckerClassEntry typeEntry;
		try {
			typeEntry = symbolTable.retrieveClass(type);
			symbolTable.enterVariable(identifier, new CheckerVariableEntry(typeEntry, false));
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (typeEntry.equals(voidType)) {
			throw new CheckerException("Variable declaration with Void type");
		}
	}

	/**
	 * Called after the entire tree is processed.
	 */
	@Override
	public void end() {
		symbolTable.closeScope();
		returnTypeStack.pop();
	}

	/**
	 * Called after the occurrence of an expression statement.
	 * 
	 * @param expr
	 *            the resulting type from the expression.
	 */
	@Override
	public void expressionStatement(CheckerClassEntry expr) {
		setReturnType(expr);
	}

	/**
	 * Called after the occurrence of an argument in a function declaration.
	 * 
	 * @param position
	 *            the position of the argument, starting at 0 for the first
	 *            argument.
	 * @param type
	 *            the type of argument variable.
	 * @param identifier
	 *            the identifier of the argument variable.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void functionArgument(int position, String type, String identifier) throws CheckerException {
		CheckerClassEntry typeEntry;
		try {
			typeEntry = symbolTable.retrieveClass(type);
			symbolTable.enterVariable(identifier, new CheckerVariableEntry(typeEntry, true));
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (typeEntry.equals(voidType)) {
			throw new CheckerException("Argument declaration with Void type");
		}
		currentFunction.addParameter(typeEntry);
	}

	/**
	 * Called after the arguments and before the function body in a function
	 * declaration.
	 * 
	 * @param returnType
	 *            the identifier of the function return value.
	 * @param functionName
	 *            the identifier of the function.
	 */
	@Override
	public void functionBody(String returnType, String functionName) {
		// Do nothing.
	}

	/**
	 * Called after the occurrence of an argument in a function call.
	 * 
	 * @param call
	 *            the resulting object from the matching call to
	 *            functionCallStart.
	 * @param position
	 *            the position of the argument, starting at 0 for the first
	 *            argument.
	 * @param expr
	 *            the resulting type from expression containing the value for
	 *            this argument.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void functionCallArgument(CheckerFunctionEntry call, int position, CheckerClassEntry expr)
			throws CheckerException {
		if (position < call.getParameters().size() && !call.getParameters().get(position).equals(expr)) {
			throw new CheckerException("Argument is not of matching type");
		}
	}

	/**
	 * Called at the end of a function call.
	 * 
	 * @param call
	 *            the resulting object from the matching call to
	 *            functionCallStart.
	 * @param argumentCount
	 *            the total number of arguments passed to the function.
	 * @param expr
	 *            the resulting type from the expression on which this member
	 *            function is to be executed.
	 * @param functionName
	 *            the identifier of the function to be called.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry functionCallEnd(CheckerFunctionEntry call, int argumentCount, CheckerClassEntry expr,
			String functionName) throws CheckerException {
		CheckerFunctionEntry functionEntry;
		try {
			functionEntry = symbolTable.retrieveMemberFunction(expr, functionName);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (argumentCount != functionEntry.getParameters().size()) {
			throw new CheckerException("Function call with wrong number of arguments");
		}
		return functionEntry.getReturnType();
	}

	/**
	 * Called at the end of a function call.
	 * 
	 * @param call
	 *            the resulting object from the matching call to
	 *            functionCallStart.
	 * @param argumentCount
	 *            the total number of arguments passed to the function.
	 * @param functionName
	 *            the identifier of the function to be called.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry functionCallEnd(CheckerFunctionEntry call, int argumentCount, String functionName)
			throws CheckerException {
		CheckerFunctionEntry functionEntry;
		try {
			functionEntry = symbolTable.retrieveFunction(functionName);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (argumentCount != functionEntry.getParameters().size()) {
			throw new CheckerException("Function call with wrong number of arguments");
		}
		return functionEntry.getReturnType();
	}

	/**
	 * Called at the start of a function call.
	 * 
	 * @param expr
	 *            the resulting type from the expression on which this member
	 *            function is to be executed.
	 * @param functionName
	 *            the identifier of the function to be called.
	 * @return an object that will be passed to all calls related to this
	 *         function call.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerFunctionEntry functionCallStart(CheckerClassEntry expr, String functionName) throws CheckerException {
		try {
			return symbolTable.retrieveMemberFunction(expr, functionName);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called at the start of a function call.
	 * 
	 * @param functionName
	 *            the identifier of the function to be called.
	 * @return an object that will be passed to all calls related to this
	 *         function call.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerFunctionEntry functionCallStart(String functionName) throws CheckerException {
		try {
			return symbolTable.retrieveFunction(functionName);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called at the end of a function declaration.
	 * 
	 * @param returnType
	 *            the identifier of the function return value.
	 * @param functionName
	 *            the identifier of the function.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void functionEnd(String returnType, String functionName) throws CheckerException {
		if (!returnTypeStack.pop().equals(currentFunction.getReturnType())
				&& !currentFunction.getReturnType().equals(voidType)) {
			throw new CheckerException("Function declaration with invalid return type");
		}
		currentFunction = null;
		symbolTable.closeScope();
	}

	/**
	 * Called at the start of a function declaration.
	 * 
	 * @param returnType
	 *            the identifier of the function return value.
	 * @param functionName
	 *            the identifier of the function.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void functionStart(String returnType, String functionName) throws CheckerException {
		try {
			currentFunction = new CheckerFunctionEntry(symbolTable.retrieveClass(returnType));
			symbolTable.enterFunctionAndOpenScope(functionName, currentFunction);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		returnTypeStack.push(voidType);
	}

	/**
	 * Called at the start of the else part of an if-else statement.
	 * 
	 * @param ifElse
	 *            Ignored.
	 * @param expr
	 *            the resulting type from expression containing the value for
	 *            the if-else condition.
	 */
	@Override
	public void ifElse(Object ifElse, CheckerClassEntry expr) {
		symbolTable.closeScope();
		symbolTable.openScope();
	}

	/**
	 * Called at the end of an if-else statement.
	 * 
	 * @param ifElse
	 *            Ignored.
	 * @param expr
	 *            the resulting type from expression containing the value for
	 *            the if-else condition.
	 */
	@Override
	public void ifEnd(Object ifElse, CheckerClassEntry ex) {
		symbolTable.closeScope();
		setReturnType(voidType);
	}

	/**
	 * Called at the start of an if-else statement.
	 * 
	 * @param expr
	 *            the resulting type from expression containing the value for
	 *            the if-else condition.
	 * @return null.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public Object ifStart(CheckerClassEntry expr) throws CheckerException {
		symbolTable.openScope();
		if (expr != booleanType) {
			throw new CheckerException("If-statement condition not Boolean");
		}
		return null;
	}

	/**
	 * Called at the occurrence of a boolean literal.
	 * 
	 * @param value
	 *            the boolean value of the literal.
	 * @return the resulting type.
	 */
	@Override
	public CheckerClassEntry literalBoolean(boolean value) {
		return booleanType;
	}

	/**
	 * Called at the occurrence of an integer literal.
	 * 
	 * @param value
	 *            the integer value of the literal.
	 * @return the resulting type.
	 */
	@Override
	public CheckerClassEntry literalInteger(int value) {
		return integerType;
	}

	/**
	 * Called at the occurrence of a string literal.
	 * 
	 * @param value
	 *            the string value of the literal.
	 * @return the resulting type.
	 */
	@Override
	public CheckerClassEntry literalString(String value) {
		return stringType;
	}

	/**
	 * Called at the end of the main function.
	 */
	@Override
	public void mainFunctionEnd() {
		symbolTable.closeScope();
	}

	/**
	 * Called at the start of the main function.
	 */
	@Override
	public void mainFunctionStart() {
		symbolTable.openScope();
	}

	/**
	 * Called at the occurrence of a new array creation.
	 * 
	 * @param type
	 *            the type of the elements of the array.
	 * @param expr
	 *            the resulting type from expression containing the size for the
	 *            new array.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry newArray(String type, CheckerClassEntry expr) throws CheckerException {
		if (!expr.equals(integerType)) {
			throw new CheckerException("Array creation with non-integer size");
		}
		try {
			return symbolTable.retrieveClass(type).getArrayTypeOfThisClass();
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called at the occurrence of a new instance creation.
	 * 
	 * @param type
	 *            the type of the instance
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry newObject(String type) throws CheckerException {
		try {
			return symbolTable.retrieveClass(type);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
	}

	/**
	 * Called at the occurrence of the and operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorAnd(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(booleanType)) {
			throw new CheckerException("And-operator left-hand-side is not Boolean");
		}
		if (!expr2.equals(booleanType)) {
			throw new CheckerException("And-operator right-hand-side is not Boolean");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the division operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorDivision(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType)) {
			throw new CheckerException("Division-operator left-hand-side is not Integer");
		}
		if (!expr2.equals(integerType)) {
			throw new CheckerException("Division-operator right-hand-side is not Integer");
		}
		return integerType;
	}

	/**
	 * Called at the occurrence of the equals operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorEquals(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(expr2)) {
			throw new CheckerException("Equals operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the greater operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorGreater(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType) && !expr1.equals(stringType)) {
			throw new CheckerException("Greater-operator left-hand-side is not Integer or String");
		}
		if (!expr1.equals(expr1)) {
			throw new CheckerException("Greater-operator operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the greater-or-equals operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorGreaterEquals(CheckerClassEntry expr1, CheckerClassEntry expr2)
			throws CheckerException {
		if (!expr1.equals(integerType) && !expr1.equals(stringType)) {
			throw new CheckerException("GreaterEquals-operator left-hand-side is not Integer or String");
		}
		if (!expr1.equals(expr1)) {
			throw new CheckerException("GreaterEquals-operator operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the less operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorLess(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType) && !expr1.equals(stringType)) {
			throw new CheckerException("Less-operator left-hand-side is not Integer or String");
		}
		if (!expr1.equals(expr1)) {
			throw new CheckerException("Less-operator operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the less-or-equals operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorLessEquals(CheckerClassEntry expr1, CheckerClassEntry expr2)
			throws CheckerException {
		if (!expr1.equals(integerType) && !expr1.equals(stringType)) {
			throw new CheckerException("LessEquals-operator left-hand-side is not Integer or String");
		}
		if (!expr1.equals(expr1)) {
			throw new CheckerException("LessEquals-operator operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the minus operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorMinus(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType)) {
			throw new CheckerException("Minus-operator left-hand-side is not Integer");
		}
		if (!expr2.equals(integerType)) {
			throw new CheckerException("Minus-operator right-hand-side is not Integer");
		}
		return integerType;
	}

	/**
	 * Called at the occurrence of the modulo operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorModulo(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType)) {
			throw new CheckerException("Modulo-operator left-hand-side is not Integer");
		}
		if (!expr2.equals(integerType)) {
			throw new CheckerException("Modulo-operator right-hand-side is not Integer");
		}
		return integerType;
	}

	/**
	 * Called at the occurrence of the multiply operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorMultiply(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType)) {
			throw new CheckerException("Multiply-operator left-hand-side is not Integer");
		}
		if (!expr2.equals(integerType)) {
			throw new CheckerException("Multiply-operator right-hand-side is not Integer");
		}
		return integerType;
	}

	/**
	 * Called at the occurrence of the not-equals operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorNotEquals(CheckerClassEntry expr1, CheckerClassEntry expr2)
			throws CheckerException {
		if (!expr1.equals(expr2)) {
			throw new CheckerException("Equals operands are not the same type");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the or operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorOr(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(booleanType)) {
			throw new CheckerException("Or-operator left-hand-side is not Boolean");
		}
		if (!expr2.equals(booleanType)) {
			throw new CheckerException("Or-operator right-hand-side is not Boolean");
		}
		return booleanType;
	}

	/**
	 * Called at the occurrence of the plus operator.
	 * 
	 * @param expr1
	 *            the resulting type from left-hand side expression.
	 * @param expr2
	 *            the resulting type from right-hand side expression.
	 * @return the resulting type.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public CheckerClassEntry operatorPlus(CheckerClassEntry expr1, CheckerClassEntry expr2) throws CheckerException {
		if (!expr1.equals(integerType) && !expr1.equals(stringType)) {
			throw new CheckerException("Plus-operator left-hand-side is not Integer or String");
		}
		if (!expr1.equals(expr2)) {
			throw new CheckerException("Plus-operator operands are not the same type");
		}
		return expr1;
	}

	/**
	 * Called at the occurrence of a print statement.
	 * 
	 * @param expr
	 *            the resulting type from expression whose result should be
	 *            printed.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void print(CheckerClassEntry expr) throws CheckerException {
		if (!expr.equals(integerType) && !expr.equals(stringType) && !expr.equals(booleanType)) {
			throw new CheckerException("Print-statement with custom or void typed variable");
		}
		setReturnType(voidType);
	}

	/**
	 * Called at the occurrence of a read statement.
	 * 
	 * @param identifier
	 *            the identifier of the variable to which the resulting value
	 *            should be written.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void read(String identifier) throws CheckerException {
		CheckerVariableEntry variable;
		try {
			variable = symbolTable.retrieveVariable(identifier);
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (variable.isConstant()) {
			throw new CheckerException("Read-statement with constant value");
		}
		if (!variable.getType().equals(integerType) && !variable.getType().equals(stringType)
				&& !variable.getType().equals(booleanType)) {
			throw new CheckerException("Read-statement with custom or void typed variable");
		}
		setReturnType(voidType);
	}

	private void setReturnType(CheckerClassEntry type) {
		returnTypeStack.pop();
		returnTypeStack.push(type);
	}

	/**
	 * Called at the start of the tree.
	 * 
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void start() throws CheckerException {
		symbolTable.openScope();
		try {
			symbolTable.enterClassAndOpenScope("Integer", integerType);
			symbolTable.closeScope();
			symbolTable.enterClassAndOpenScope("String", stringType);
			symbolTable.closeScope();
			symbolTable.enterClassAndOpenScope("Boolean", booleanType);
			symbolTable.closeScope();
			symbolTable.enterClassAndOpenScope("Void", voidType);
			symbolTable.closeScope();
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		returnTypeStack.push(voidType);
	}

	/**
	 * Called at the end of a statement expression.
	 * 
	 * @param statementExpression
	 *            Ignored.
	 * @return the resulting expression object.
	 */
	@Override
	public CheckerClassEntry statementExpressionEnd(Object statementExpression) {
		symbolTable.closeScope();
		return returnTypeStack.pop();
	}

	/**
	 * Called at the start of a statement expression.
	 * 
	 * @return null.
	 */
	@Override
	public Object statementExpressionStart() {
		symbolTable.openScope();
		returnTypeStack.push(voidType);
		return null;
	}

	/**
	 * Called at the occurrence of a throw statement.
	 * 
	 * @param expr
	 *            the resulting type from expression whose result should be
	 *            thrown
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void throwException(CheckerClassEntry expr) throws CheckerException {
		if (!expr.equals(stringType)) {
			throw new CheckerException("Throw statement is not String");
		}
	}

	/**
	 * Called at the end of the catch part of a try-catch statement.
	 * 
	 * @param tryCatch
	 *            Ignored.
	 * @param type
	 *            the type of the catch variable.
	 * @param identifier
	 *            the identifier of the catch variable.
	 */
	@Override
	public void tryCatchEnd(Object tryCatch, String type, String identifier) {
		symbolTable.closeScope();
		setReturnType(voidType);
	}

	/**
	 * Called at the start of the catch part of a try-catch statement.
	 * 
	 * @param tryCatch
	 *            Ignored.
	 * @param type
	 *            the type of the catch variable.
	 * @param identifier
	 *            the identifier of the catch variable.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void tryCatchStart(Object tryCatch, String type, String identifier) throws CheckerException {
		symbolTable.openScope();
		CheckerClassEntry typeEntry;
		try {
			typeEntry = symbolTable.retrieveClass(type);
			symbolTable.enterVariable(identifier, new CheckerVariableEntry(typeEntry, true));
		} catch (SymbolTableException e) {
			throw new CheckerException(e);
		}
		if (!typeEntry.equals(stringType)) {
			throw new CheckerException("Catch variable is not String");
		}
	}

	/**
	 * Called at the end of the try part of a try-catch statement.
	 * 
	 * @param tryCatch
	 *            Ignored.
	 */
	@Override
	public void tryEnd(Object tryCatch) {
		symbolTable.closeScope();
	}

	/**
	 * Called at the start of the try part of a try-catch statement.
	 * 
	 * @return null.
	 */
	@Override
	public Object tryStart() {
		symbolTable.openScope();
		return null;
	}

	/**
	 * Called after the condition of a while-do statement.
	 * 
	 * @param whileDo
	 *            Ignored.
	 * @param expr
	 *            the resulting type from condition expression.
	 * @throws CheckerException
	 *             if an errors occurs.
	 */
	@Override
	public void whileCondition(Object whileDo, CheckerClassEntry expr) throws CheckerException {
		symbolTable.openScope();
		if (expr != booleanType) {
			throw new CheckerException("While-statement condition not Boolean");
		}
	}

	/**
	 * Called at the end of a while-do statement.
	 * 
	 * @param whileDo
	 *            Ignored.
	 * @param expr
	 *            the resulting type from condition expression.
	 */
	@Override
	public void whileEnd(Object whileDo, CheckerClassEntry expr) {
		symbolTable.closeScope();
		setReturnType(voidType);
	}

	/**
	 * Called at the start of a while-do statement.
	 * 
	 * @return null.
	 */
	@Override
	public Object whileStart() {
		// Do nothing.
		return null;
	}
}
